﻿#region Includes
using System;
using OpenTK;
using OpenTK.Input;
using OpenTK.Graphics.OpenGL;
using GingerGL.Input;
#endregion

namespace GingerGL.Graphics
{
    //------------------------------------------------------------//
    /// <summary>
    /// First Person Camera
    /// </summary>
    public class FirstPersonCamera : Camera
    {
        //--------------------------------------------------------//
        #region Variables
        private Matrix4 Transform;
        #endregion
        //--------------------------------------------------------//
        #region Properties
        /// <summary>
        /// Gets Camera Tilt
        /// </summary>
        public float Pitch { get; protected set; }
        /// <summary>
        /// Gets Camera Spin
        /// </summary>
        public float Yaw { get; protected set; }
        /// <summary>
        /// Gets/Sets TRUE if anything has changes that would require and update
        /// </summary>
        private bool IsDirty { get; set; }
        #endregion
        //--------------------------------------------------------//
        #region Constructors
        /// <summary>
        /// Initializes a new instance of object
        /// </summary>
        public FirstPersonCamera()
        {
            this.Yaw = 0.0f;
            this.Pitch = 0.0f;
            this.Position = new Vector3(0f, 3f, 0f);
            this.View = new Vector3(0f, 0f, -1f);
            this.Up = new Vector3(0f, 1f, 0f);
            this.Rotate(0f, 0f);
            IsDirty = true;
        }
        #endregion
        //--------------------------------------------------------//
        #region Methods
        /// <summary>
        /// Moves Camera Forward
        /// </summary>
        public override void Move(MoveDirection direction)
        {
            Vector3 vector = this.Forward * ((direction == MoveDirection.Forward) ? this.MoveSpeed : -this.MoveSpeed) * (float)Engine.Instance.LastFrameTime;
            vector.Y = 0;
            this.Position += vector;
            this.View += vector;
            this.IsDirty = true;
        }
        /// <summary>
        /// Moves Camera Right
        /// </summary>
        public override void Displace(DisplaceDirection direction)
        {
            Vector3 v = this.Right * ((direction == DisplaceDirection.Right) ? this.DisplaceSpeed : -this.DisplaceSpeed) * (float)Engine.Instance.LastFrameTime;
            this.Position += v;
            this.View += v;
            this.IsDirty = true;
        }
        /// <summary>
        /// Moves Camera Up
        /// </summary>
        public override void Elevate(ElevationDirection direction)
        {
            Vector3 v = new Vector3(0, ((direction == ElevationDirection.Ascend) ? this.ElevateSpeed : -this.ElevateSpeed) * (float)Engine.Instance.LastFrameTime, 0);
            this.Position += v;
            this.View += v;
            this.IsDirty = true;
        }
        /// <summary>
        /// Sets Cameras Position
        /// </summary>
        public override void Set(Vector3 pos)
        {
            this.View = (this.View + pos) - this.Position;
            this.Position = pos;
            this.IsDirty = true;
        }
        /// <summary>
        /// Turn Camera
        /// </summary>
        public void Rotate(float yaw, float pitch) 
        { 
            this.Pitch += pitch; 
            this.Yaw -= yaw;

            if (this.Pitch > 90f) this.Pitch = 90f;
            if (this.Pitch < -90f) this.Pitch = -90f;

            this.View = new Vector3(
                this.Position.X + MathHelper2.SinDeg(this.Yaw),
                this.Position.Y + MathHelper2.SinDeg(this.Pitch),
                this.Position.Z - MathHelper2.CosDeg(this.Yaw));
            this.IsDirty = true;
        }
        /// <summary>
        /// Set Camera
        /// </summary>
        public void Set(Vector3 pos, float pitch, float yaw)
        {
            this.Set(Position);
            this.Pitch = pitch;
            this.Yaw = yaw;
            this.IsDirty = true;
        }
        /// <summary>
        /// Updates Camera View
        /// </summary>
        protected override void Update()
        {
            if (IsDirty)
            {
                this.Transform = Matrix4.LookAt(this.Position + this.Offset, this.View + this.Offset, this.Up);
                // Select The Modelview Matrix
                GL.MatrixMode(MatrixMode.Modelview);
                // Apply Matrix
                GL.LoadMatrix(ref Transform);
                GL.Translate(this.Offset);
                //Update Frustum
                UpdateFrustum();
                IsDirty = false;
            }
            else
            {
                GL.MatrixMode(MatrixMode.Modelview);
                GL.LoadMatrix(ref Transform);
            }
        }
        /// <summary>
        /// Resets Camera
        /// </summary>
        public override void Reset()
        {
            this.Yaw = 0.0f;
            this.Pitch = 0.0f;
            this.Position = new Vector3(0f, 0f, 0f);
            this.View = new Vector3(0f, 0f, 1f);
            this.Up = new Vector3(0f, 1f, 0f);
            this.Rotate(0f, 0f);
            this.IsDirty = true;
        }
        /// <summary>
        /// Mouse Move
        /// </summary>
        public override void MouseMove(MouseMoveEventArgs e)
        {
            this.Rotate(
                (((float)e.XDelta) * this.RotateSpeed * (float)Engine.Instance.LastFrameTime) * 100,
                (((float)e.YDelta) * this.RotateSpeed * (float)Engine.Instance.LastFrameTime) * 100);
            this.IsDirty = true;
        }
        /// <summary>
        /// First Person Camera ToString
        /// </summary>
        public override string ToString()
        {
            return String.Format("{0} Pitch: {1} Yaw: {2}", base.ToString(), this.Pitch, this.Yaw);
        }
        /// <summary>
        /// Gets Hashcode
        /// </summary>
        public override int GetHashCode()
        {
            return base.GetHashCode() 
                    ^ this.Pitch.GetHashCode() 
                    ^ this.Yaw.GetHashCode();
        }
        #endregion
        //--------------------------------------------------------//
    }
    //------------------------------------------------------------//
}
